# -*- coding: utf-8 -*-
#!usr/bin/python3
# Author: Hermit_Yoshino
'''
@author: Hermit_Yoshino
@license: no
@contact: Efreet_Itsukakotori@hotmail.com
@file: kmeans.py
@time: 20-5-18 下午3:22
@desc:
'''

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 1. 随机选择K个中心点
# 2. 把每个数据点分配到离它最近的中心点；
# 3. 重新计算每类中的点到该类中心点距离的平均值
# 4. 分配每个数据到它最近的中心点；
# 5. 重复步骤3和4，直到所有的观测值不再被分配或是达到最大的迭代次数（R把10次作为默认迭代次数）。

def csv_read(file = './iris_training.csv'):
    '''
    read file
    :param file:
    :return: df
    '''
    tfr_data = pd.read_csv(file, encoding='gbk', engine='python', chunksize=1000
                              , iterator=False)
    return tfr_data.get_chunk(1000)

def euclidean_distance(li_vecs, vec2):
    '''
    欧几里得距离
    :param vec1:
    :param vec2:
    :return: np
    '''
    # np.linalg.norm(vec1 - vec2)
    # np.sqrt(np.sum(np.square(vec1 - vec2)))
    li = []
    for vec1 in li_vecs:
        li.append(np.sqrt(np.sum(np.square(vec1 - vec2))))
    return np.asarray(li)

def angle_cosine(li_vecs, vec2):
    '''
    夹角余弦
    :param vec1:
    :param vec2:
    :return:
    '''
    li = []
    for vec1 in li_vecs:
        li.append(np.dot(vec1, vec2) / (np.linalg.norm(vec1) * (np.linalg.norm(vec2))))
    return np.asarray(li)
    # return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * (np.linalg.norm(vec2)))

def rand_cent(df_data, k):
    '''
    随机生成初始的质心
    :param dataSet:
    :param k:
    :return:
    '''
    # 已有随机点
    df_data = df_data.sample(frac=1)
    df_data = df_data.sample(frac=1)
    df_data = df_data.sample(frac=1)
    df_data = df_data.sample(frac=1)
    return df_data[0:k].values

    # 范围随机点
    # return np.random.uniform(df_data.min().values.min(), df_data.max().values.max(), (k, df_data.shape[1]))

def center_point(df_data):
    '''
    计算质心
    :param df_data: 原点数据（含类别）
    :return: 质心np
    '''
    li_center = []
    for i in df_data.iloc[:, -1].value_counts.values:
        _df_data_i = df_data[df_data.iloc[:, -1].isin([i])]
        li_center.append(_df_data_i.sum().values / _df_data_i.shape[0])
    return np.asarray(li_center)


def calculate_distance(df_data, li_center, distance, i=1):
    '''
    计算距离
    :param df_data: 原点数据
    :param li_center: 质心点数组
    :param distance: 距离计算方法
    :param i:
    :return: np
    '''
    li_distance = []
    for center in li_center:
        li_distance.append(distance(df_data.iloc[:, 0: df_data.shape[1] - i].values, center))
    return np.asarray(li_distance).T

def recently_classified(df_data, np_distance, i=1):
    '''
    分配最近
    :param df_data:
    :param i:
    :return: df
    '''
    near_distance = []
    for _distance in np_distance:
        near_distance.append(np.argmin(_distance))
    df_data[df_data.shape[1] - i] = near_distance
    return df_data


def k_means(df_data, k, n=1000, distance=euclidean_distance):
    '''
    k聚类
    :param df_data:
    :param k: 几个类
    :param n: 迭代几次
    :param distance: 距离方法
    :return:
    '''
    li_center = rand_cent(df_data, k)
    np_distance = calculate_distance(df_data, li_center, distance, 0)
    df_data = recently_classified(df_data, np_distance, 0)

    for i in range(n):
        np_distance = calculate_distance(df_data, li_center, distance)
        df_data = recently_classified(df_data, np_distance)

    return df_data


def show(df_data, colors):
    '''

    :param df_data:
    :param colors:
    :return:
    '''
    for i in range(len(colors)):
        x = df_data[df_data.iloc[:, -1].isin([i])].iloc[:, 0].values
        y = df_data[df_data.iloc[:, -1].isin([i])].iloc[:, 1].values
        plt.scatter(x, y, c=colors[i])
        # , s=scale, label=color,
        #        alpha=0.3, edgecolors='none')
    plt.show()


def main(i=1):
    k = 3
    df_data = csv_read()
    if i == 0:
        df_data = k_means(df_data.iloc[:, 0: 4], k, n=100)
    else:
        df_data = k_means(df_data.iloc[:, 0: 4], 3, n=100, distance=angle_cosine)
    show(df_data, ['red', 'blue', 'green'])


if __name__ == '__main__':
    main()